package com.gabejeff.engine;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import com.gabejeff.exceptions.InvalidSceneException;
import com.gabejeff.exceptions.TileNotFoundException;

public class TiledScene extends Scene {

	static int tileSize;
	
	private BufferedImage scene = null;
	private Graphics2D g2d = null;
	
	private final static Color salmon = new Color(255,226,215);
	
	private int[][] sceneMatrix;
	protected int rows;
	protected int cols;
	
	private Player player = new Player();        
    private static TileFactory tf = TileFactory.getInstance();

	/**
	 * Constructor
	 * @param rows Number of rows in the scene matrix
	 * @param cols Number of columns in the scene matrix
	 */
	public TiledScene(int rows, int cols) {
		
		this.rows = rows;
		this.cols = cols;
		this.sceneMatrix = new int[rows][cols];
		clearMatrix();
		
	}
	
	/**
	 * Constructor
	 * @param matrix The scene matrix
	 * @param tileSize The size of the tiles in pixels
	 */
	public TiledScene(int[][] matrix, int t) {
		
		this.sceneMatrix = matrix.clone();
		this.rows = matrix.length;
		this.cols = matrix[0].length;
		
		tileSize = t;
		
	}
	
	/**
	 * Sets the value of the tile at a specific location
	 * @param row The row, or y-value of the tile
	 * @param col The column, or x-value of the tile
	 * @param val The value of the tile
	 * @throws InvalidSceneException Thrown if the row and column values are out of range
	 */
	public void setTileVal(int row, int col, int val) throws InvalidSceneException {
		
		if (row < 0 || row > rows-1 || col < 0 || col > cols-1 /*|| !isValid(val) */) {
			throw new InvalidSceneException();
		}
		
		sceneMatrix[row][col] = val;
		
	}
	
	/**
	 * Gets the value of a tile at a specific location
	 * @param row The row, or y-value of the tile
	 * @param col The column, or x-value of the tile
	 * @return The value at <row, column> in the scene matrix
	 * @throws InvalidSceneException Thrown if the row and column values are out of range
	 */
	public int getTileVal(int row, int col) throws InvalidSceneException {
		
		if (row < 0 || row > rows-1 || col < 0 || col > cols-1) {
			throw new InvalidSceneException();
		}
		
		return sceneMatrix[row][col];
		
	}
	
	/**
	 * Returns a portion of the scene
	 * @param y The starting row of the subset
	 * @param x The starting column of the subset
	 * @param height The number of rows within the subscene
	 * @param width The number of columns within the subscene
	 * @return The subscene specified by the parameters
	 * @throws InvalidSceneException Thrown if the row and column values are out of range
	 */
	public Scene getSubScene(int x, int y, int width, int height) throws InvalidSceneException {
		
		if (y + height > this.rows || x + width > this.cols || 
		    y < 0 || x < 0 || height < 0 || width < 0) {
			throw new InvalidSceneException();
		}
		
		int[][] subMatrix = new int[height][width];
		for (int row = 0; row < height; row++) {
			for (int col = 0; col < width; col++) {
				subMatrix[row][col] = this.sceneMatrix[y+row][x+col];
			}
		}
		
		Scene subScene = new TiledScene(subMatrix, tileSize);
		
		return subScene;
		 
	}
	
	/**
	 * Sets the tile size in pixels
	 * @param tileSize The tile size
	 */
	public static void setTileSize(int t) {
		
		tileSize = t;
		
	}
	
	public static int getTileSize() {
		return tileSize;
	}
	
	/**
	 * Generates a scene BufferedImage from the scene matrix
	 * @return The scene as a BufferedImage
	 */
	protected BufferedImage render() {
		
		dim.setSize(getFrame().getWidth(), getFrame().getHeight());
		
		if (scene == null ||
			scene.getWidth() != tileSize * this.cols ||
			scene.getHeight() != tileSize * this.rows) {
			
			scene = new BufferedImage(tileSize * this.cols, 
                    								tileSize * this.rows, 
                    								BufferedImage.TYPE_INT_ARGB);
		}
		
		g2d = scene.createGraphics();
		
		/* Ideally these values should be user customizable for speed vs. quality */
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        /*--------------------*/
		
		int visibleRows = (int)(dim.getHeight() / tileSize) + 1;
		int visibleCols = (int)(dim.getWidth() / tileSize) + 1;
		
		int rowOff = player.getPosition().y;
		int colOff = player.getPosition().x;

		for (int row=0;row<visibleRows;row++) {
			for (int col=0;col<visibleCols;col++) {
				
				if (sceneMatrix[row][col] == -1 ||
					(row + rowOff < 0 || row + rowOff > this.rows - 1 ||
				    col + colOff < 0 || col + colOff > this.cols - 1)) {
					drawCheckerTile(g2d, row, col);
					continue;
				}
				
				try {
					g2d.drawImage(tf.produceTile(sceneMatrix[row + rowOff][col + colOff]), 
							                     (col * tileSize), 
							                     (row * tileSize), 
							                     tileSize, 
							                     tileSize, null);
				} catch (TileNotFoundException ex) {
					drawCheckerTile(g2d, row, col);
				}
				
			}
		}
				
		return scene;
		
	}
	
	private void drawCheckerTile(Graphics2D g2d, int row, int col) {
		if (row % 2 == 0 && col % 2 == 0 || row % 2 == 1 && col % 2 == 1) {
			g2d.setColor(salmon);
		} else {
			g2d.setColor(salmon.brighter());
		}
		g2d.fillRect((col * tileSize), 
				     (row * tileSize), 
				     tileSize, 
				     tileSize);
	}
	
	/**
	 * Sets the values of each entry in the scene matrix to -1
	 */
	private void clearMatrix() {
		
		for (int row = 0; row < this.rows; row++) {
			for (int col = 0; col < this.cols; col++) {
				this.sceneMatrix[row][col] = -1;
			}
		}
		
	}

	public boolean isTileOpen( double row , double col )
	{
		return true;
//		return getTile( row , col ).isTraversable();
	}

	public void moveUp()
	{
		Point p = player.getPosition();
		if( isTileOpen( p.getX() + 1 , p.getY() - 1) )
		{
			player.setPosition( p.getX()  , p.getY() - 1 );
		}
	}

	public void moveDown()
	{
		Point p = player.getPosition();
		if( isTileOpen( p.getX() , p.getY() + 1 ) )
		{
			player.setPosition( p.getX(), p.getY() + 1 );
		}
	}

	public void moveLeft()
	{
		Point p = player.getPosition();
		if( isTileOpen( p.getX() - 1 , p.getY() ) )
		{
			player.setPosition( p.getX() - 1 , p.getY() );
		}
	}

	public void moveRight()
	{
		Point p = player.getPosition();
		if( isTileOpen( p.getX() + 1 , p.getY() ) )
		{
			player.setPosition( p.getX() + 1 , p.getY() );
		}
	}

	@Override
	public void performMenuAction(boolean escape) {
		// TODO Auto-generated method stub
		
	}

}
